require 'httmultiparty'

module Agents
  class TelegramAgent < Agent
    include FormConfigurable

    cannot_be_scheduled!
    cannot_create_events!
    no_bulk_receive!
    can_dry_run!

    description <<-MD
      Telegram Agent接收并收集事件并通过Telegram发送

      假设事件具有文本，照片，音频，文档或视频密钥。 如果您的事件未提供这些密钥，则可以使用EventFormattingAgent。

      文本密钥的值以纯文本消息的形式发送。 您还可以告诉Telegram如何使用parse_mode解析消息，设置为html或markdown。 照片，音频，文档和视频密钥的值应该是一个URL，其内容将发送给您。

      **步骤**

      * 通过创建新的bot来获取auth_token。
      * 如果您想向公共频道发送消息：
        * 以管理员身份将您的机器人添加到频道
      * 如果您要向群组发送消息：
        * 将bot添加到组中
      * 如果您想私下向自己发送消息：
        * 通过访问https://telegram.me/YourHuginnBot打开机器人保护
      * 向机器人，组或频道发送消息.
      * 从下拉列表中选择chat_id

      **配置项**

      * `caption`: 媒体内容标题（0-200个字符）
      * `disable_notification`:  在频道中静默发送消息
      * `disable_web_page_preview`: 禁用文本消息中链接的链接预览
      * `long_message`: 截断（默认）或拆分超出Telegram API限制的文本消息和标题。 Markdown和HTML标记不能跨越消息，如果没有正确打开或关闭，将呈现为纯文本
      * `parse_mode`: 解析文本消息的策略

      有关详细信息，请参阅Telegram Bot API官方文档。
    MD

    def default_options
      {
        auth_token: 'xxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
        chat_id: 'xxxxxxxx'
      }
    end

    form_configurable :auth_token, roles: :validatable
    form_configurable :chat_id, roles: :completable
    form_configurable :caption
    form_configurable :disable_notification, type: :array, values: ['', 'true', 'false']
    form_configurable :disable_web_page_preview, type: :array, values: ['', 'true', 'false']
    form_configurable :long_message, type: :array, values: ['', 'split', 'truncate']
    form_configurable :parse_mode, type: :array, values: ['', 'html', 'markdown']

    def validate_auth_token
      HTTMultiParty.post(telegram_bot_uri('getMe'))['ok']
    end

    def complete_chat_id
      response = HTTMultiParty.post(telegram_bot_uri('getUpdates'))
      return [] unless response['ok']
      response['result'].map { |update| update_to_complete(update) }.uniq
    end

    def validate_options
      errors.add(:base, 'auth_token is required') unless options['auth_token'].present?
      errors.add(:base, 'chat_id is required') unless options['chat_id'].present?
      errors.add(:base, 'caption should be 200 characters ol less') if interpolated['caption'].present? && interpolated['caption'].length > 200 && (!interpolated['long_message'].present? || interpolated['long_message'] != 'split')
      errors.add(:base, "disable_notification has invalid value: should be 'true' or 'false'") if interpolated['disable_notification'].present? && !%w(true false).include?(interpolated['disable_notification'])
      errors.add(:base, "disable_web_page_preview has invalid value: should be 'true' or 'false'") if interpolated['disable_web_page_preview'].present? && !%w(true false).include?(interpolated['disable_web_page_preview'])
      errors.add(:base, "long_message has invalid value: should be 'split' or 'truncate'") if interpolated['long_message'].present? && !%w(split truncate).include?(interpolated['long_message'])
      errors.add(:base, "parse_mode has invalid value: should be 'html' or 'markdown'") if interpolated['parse_mode'].present? && !%w(html markdown).include?(interpolated['parse_mode'])
    end

    def working?
      received_event_without_error? && !recent_error_logs?
    end

    def receive(incoming_events)
      incoming_events.each do |event|
        receive_event event
      end
    end

    private

    TELEGRAM_ACTIONS = {
      text:     :sendMessage,
      photo:    :sendPhoto,
      audio:    :sendAudio,
      document: :sendDocument,
      video:    :sendVideo
    }.freeze

    def configure_params(params)
      params[:chat_id] = interpolated['chat_id']
      params[:disable_notification] = interpolated['disable_notification'] if interpolated['disable_notification'].present?
      if params.has_key?(:text)
        params[:disable_web_page_preview] = interpolated['disable_web_page_preview'] if interpolated['disable_web_page_preview'].present?
        params[:parse_mode] = interpolated['parse_mode'] if interpolated['parse_mode'].present?
      else
        params[:caption] = interpolated['caption'] if interpolated['caption'].present?
      end

      params
    end

    def receive_event(event)
      interpolate_with event do
        messages_send = TELEGRAM_ACTIONS.count do |field, _method|
          payload = event.payload[field]
          next unless payload.present?
          send_telegram_messages field, configure_params(field => payload)
          true
        end
        error("No valid key found in event #{event.payload.inspect}") if messages_send.zero?
      end
    end

    def send_message(field, params)
      response = HTTMultiParty.post telegram_bot_uri(TELEGRAM_ACTIONS[field]), query: params
      unless response['ok']
        error(response)
      end
    end

    def send_telegram_messages(field, params)
      if interpolated['long_message'] == 'split'
        if field == :text
          params[:text].scan(/\G(?:\w{4096}|.{1,4096}(?=\b|\z))/m) do |message|
            send_message field, configure_params(field => message.strip) unless message.strip.blank?
          end
        else
          caption_array = params[:caption].scan(/\G(?:\w{200}|.{1,200}(?=\b|\z))/m)
          params[:caption] = caption_array.first.strip
          send_message field, params
          caption_array.drop(1).each do |caption|
            send_message(:text, configure_params(text: caption.strip)) unless caption.strip.blank?
            end
        end
      else
        params[:caption] = params[:caption][0..199] if params[:caption]
        params[:text] = params[:text][0..4095] if params[:text]
        send_message field, params
      end
    end

    def telegram_bot_uri(method)
      "https://api.telegram.org/bot#{interpolated['auth_token']}/#{method}"
    end

    def update_to_complete(update)
      chat = (update['message'] || update.fetch('channel_post', {})).fetch('chat', {})
      {id: chat['id'], text: chat['title'] || "#{chat['first_name']} #{chat['last_name']}"}
    end
  end
end
